﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using System.Drawing.Imaging;

namespace LowLevelGraphics.Filter
{
    /// <summary>
    /// Antivignetting is another word for "illumination normalization"
    /// see also filter class MultiscaleRetinex
    /// </summary>
    public class Antivignetting : BaseImageFilter
    {
        protected double m_dDensity = 10;
        protected double m_dPower = 3;
        protected double m_dRadius = 2;
        protected int m_nXshift = 0;
        protected int m_nYshift = 0;
        protected bool m_bNormalize = false;
        protected bool m_bUseGrayScale = false;

        /// <summary>
        /// Initializes a new instance of the <see cref="Antivignetting"/> class.
        /// </summary>
        public Antivignetting()
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="Antivignetting"/> class.
        /// </summary>
        /// <param name="_antivignetting">The _antivignetting.</param>
        internal Antivignetting(Antivignetting _antivignetting)
        {
            m_dDensity = _antivignetting.m_dDensity;
            m_dPower = _antivignetting.m_dPower;
            m_dRadius = _antivignetting.m_dRadius;
            m_nXshift = _antivignetting.m_nXshift;
            m_nYshift = _antivignetting.m_nYshift;
            m_bNormalize = _antivignetting.m_bNormalize;
            m_bUseGrayScale = _antivignetting.m_bUseGrayScale;
        }


        /// <summary>
        /// Gets or sets the density.
        /// </summary>
        /// <value>The density.</value>
        public double Density
        {
            get { return m_dDensity; }
            set { m_dDensity = value; }
        }

        /// <summary>
        /// Gets or sets the power.
        /// </summary>
        /// <value>The power.</value>
        public double Power
        {
            get { return m_dPower; }
            set { m_dPower = value; }
        }

        /// <summary>
        /// Gets or sets the radius.
        /// </summary>
        /// <value>The radius.</value>
        public double Radius
        {
            get { return m_dRadius; }
            set { m_dRadius = value; }
        }

        /// <summary>
        /// Gets or sets the X shift.
        /// </summary>
        /// <value>The X shift.</value>
        public int XShift
        {
            get { return m_nXshift; }
            set { m_nXshift = value; }
        }

        /// <summary>
        /// Gets or sets the Y shift.
        /// </summary>
        /// <value>The Y shift.</value>
        public int YShift
        {
            get { return m_nYshift; }
            set { m_nYshift = value; }
        }

        /// <summary>
        /// Gets or sets a value indicating whether this <see cref="Antivignetting"/> is normalize.
        /// </summary>
        /// <value><c>true</c> if normalize; otherwise, <c>false</c>.</value>
        public bool Normalize
        {
            get { return m_bNormalize; }
            set { m_bNormalize = value; }
        }

        /// <summary>
        /// Gets or sets a value indicating whether [use gray scale].
        /// </summary>
        /// <value><c>true</c> if [use gray scale]; otherwise, <c>false</c>.</value>
        public bool UseGrayScale
        {
            get { return m_bUseGrayScale; }
            set { m_bUseGrayScale = value; }
        }

        /// <summary>
        /// Execute this Filter
        /// A Debug Statement is written if a concrete Filter is called
        /// </summary>
        /// <param name="_bitmap"></param>
        /// <returns></returns>
        public override UnsafeBitmap Execute(UnsafeBitmap _bitmap)
        {
            UnsafeBitmap m_destImage = _bitmap;
            UnsafeBitmap m_orgImage = (UnsafeBitmap)_bitmap.Clone();
            int progress;
            int x, y, xd, td, yd, p;
            int i, xsize, ysize, diagonal, erad, xctr, yctr;
            double[] ldens;

            int Width = _bitmap.Width;
            int Height = _bitmap.Height;

            // Determine the radius of the filter.  This is the half diagonal
            // measure of the image multiplied by the command line radius factor. 

            xsize = (Height + 1) / 2;
            ysize = (Width + 1) / 2;
            erad = (int)((Math.Sqrt((xsize * xsize) + (ysize * ysize)) + 0.5) * m_dRadius);

            // Build the in-memory table which maps distance from the
            // center of the image (as adjusted by the X and Y offset,
            // if any) to the density of the filter at this remove.  This
            // table needs to be as large as the diagonal from the
            // (possibly offset) center to the most distant corner
            // of the image. 

            xsize = ((Height + 1) / 2) + Math.Abs(m_nXshift);
            ysize = ((Width + 1) / 2) + Math.Abs(m_nYshift);
            diagonal = ((int)(Math.Sqrt((xsize * xsize) + (ysize * ysize)) + 0.5)) + 1;

            ldens = new double[diagonal];

            for (i = 0; (i < diagonal); i++)
            {
                if (i >= erad)
                {
                    ldens[i] = 1;
                }
                else
                {
                    ldens[i] = (1.0 + (m_dDensity - 1) * Math.Pow(1.0 - (((double)i) / (erad - 1)), m_dPower));
                }
            }

            xctr = ((Height + 1) / 2) + m_nXshift;
            yctr = ((Width + 1) / 2) + m_nYshift;

            for (y = 0; y < Height; y++)
            {
                yd = Math.Abs(yctr - y);

                for (x = 0; x < Width; x++)
                {                   
                    xd = Math.Abs(xctr - x);
                    td = (int)(Math.Sqrt((xd * xd) + (yd * yd)) + 0.5);

                    Color color = m_orgImage.GetPixel(x, y);
                    if (m_bUseGrayScale)       // 8 bits image
                    {
                        m_destImage.SetPixel(x, y, Color.FromArgb((int)(color.R / ldens[td]), (int)(color.G / ldens[td]), (int)(color.B / ldens[td])));
                    }
                    else   // 32 bits image.
                    {
                        if (td > ldens.Length-1) continue;
                        if (ldens[td] == 0)
                        {
                            m_destImage.SetPixel(x, y, Definitions.White);
                        }
                        else
                        {
                            m_destImage.SetPixel(x, y, Color.FromArgb((int)(color.R / ldens[td]), (int)(color.G / ldens[td]), (int)(color.B / ldens[td])));
                        }
                    }
                }
            }

            // Normalize colors for a best rendering.   
            if (m_bNormalize)
            {
                new Normalize().Execute(_bitmap);
            }

            return _bitmap;
        }

        /// <summary>
        /// This is used to clone a filter, each filter must implement it's own clone functionality
        /// </summary>
        /// <returns></returns>
        public override object Clone()
        {
            return new Antivignetting(this);
        }
    }
}
